home *** CD-ROM | disk | FTP | other *** search
-
- B A S I C T R A I N I N G
- Part Two
- by Steve Estvanik
-
- Part 5. GOSUBS and FUNCTIONS
- This time we'll be examining GOSUB's and FUNCTIONS. Up to now, when we've
- wanted to repeat a section of code, we've had 2 choices. We could just copy
- the code over, or we could set up a loop. Sometimes that still leaves us
- with a messy solution.
-
- WHY GOSUBS?
- In the previous chapter, one of the exercises involved writing a short
- program that took as input a pair of numbers -- month and day, then
- calculated the number of days elapsed since the start of the year and the
- number of days till the end of the year.
-
- But what if we want to input 2 sets of numbers? One way would be to repeat
- the code. Another would be by using a GOSUB. This is a special command
- that tells the program to jump to a particular line. It differs from a GOTO
- in that when the command RETURN is found, the program jumps back to the
- statement that called it. This lets a whole section of code be used in
- several places. Thus
-
- 10 X = 1
- 20 GOSUB 100
- 30 X = 2
- 40 GOSUB 100
- 50 X = 3
- 60 GOSUB 100
- 70 END
- 100 PRINT "X squared is";X*X
- 110 RETURN
-
- This will produce 3 lines showing the squares of 1, 2 and 3. Note the use
- of the END statement. Take it out and see what happens.
-
- We can turn the elapsed days calculation into a GOSUB as follows:
-
- 10 'julian.bas
- 20 DIM DAYS(12)
- 30 DATA 31,28,31, 30,31,30, 31,31,30, 31,30,31
- 40 FOR I = 1 TO 12 : READ DAYS(I) : NEXT
- 50 INPUT "Start month";M
- 60 INPUT "Start day";D
- 70 GOSUB 200
- 80 E1 = ELAPSED
- 90 INPUT "End month";M
- 100 INPUT "End day";D
- 110 GOSUB 200
- 120 DATE.DIF = ELAPSED - E1
- 130 PRINT
- 140 PRINT "difference =";DATE.DIF
- 150 END
- 200 '===================== calc elapsed time in days
- 210 ELAPSED = D
- 220 FOR I = 1 TO M - 1
- 230 ELAPSED = ELAPSED + DAYS(I)
- 240 NEXT
- 250 PRINT ELAPSED;"days elapsed. ";365-ELAPSED; "days to go"
- 260 RETURN
-
- Here we ask for 2 sets of numbers and then calculate the difference
- between them. When working with dates for comparison you'll usually want
- to deal with the number of days past the beginning of the year, so this
- conversion routine is quite handy. This notation for dates is called
- Julian (as in Caesar).
-
-
- WE INTERRUPT THIS PROGRAM FOR A POLITICAL ANNOUNCEMENT...
- ----------------------------------------------------------
- Basic is often criticized for its lack of structure and difficulty in
- reading and maintainence. This is more of a programmer's problem though,
- not the language's. You can write structured programs in Basic just as
- you can write spaghetti code in Pascal or unmaintainable trash in C.
- Structured methods will produce good code in any of the languages. If you
- follow these guidelines you'll be able to keep your programs readable and
- easy to maintain.
-
- ** The main portion of the program should start at the top and proceed to
- the bottom. This means using FOR-NEXT and WHILE loops rather than GOTO's.
- (In older books or articles you may see the suggestion to jump to the end
- of the program for initializing or other reasons. This made a small
- amount of sense in the old days, but modern Basic interpreters are much
- more efficient and you won't notice any difference. What you will get is
- a program that's harder to work on.)
-
- ** Whenever possible, use GOSUB's and, once we've defined them, function
- calls to recycle repetive sections of programs. (Once a GOSUB is working
- you'll often be able to use them in other programs. Functions are even
- more transportable.) With a compiler, we'll use subroutines rather than
- GOSUB's for even more modularity.
-
- ** Start each GOSUB with a comment line. This should be the ONLY entry to
- a GOSUB. It's legal to enter a GOSUB at any point, but it's just asking
- for trouble. There are a very few instances where it's justified, but I'd
- put it at less than 1%. Using the comment line helps to delineate GOSUB's
- in the program.
-
- ** Similarly, the last statement in any GOSUB should be the only RETURN.
- By applying these two rules you abide by one of the central dogmas of
- structured programming -- single input, single output. This ensures that
- every time the subroutine is used it's used the same. Thus you won't get
- unpredictable results. Sometimes you'll have gosubs that finish in the
- middle of a section. Rather than succumb to the temptation to RETURN from
- that point, use a GOTO to jump down to the single RETURN at the bottom.
- This is an insignificant increase in the amount of code, but results in an
- immense increase in readability and ease of maintainence. It also makes
- it easier to trace and isolate problems. If you know there's only one
- entry and one exit, you can concentrate on the routine that's causing the
- problem. You won't have to worry what conditions are where the gosub is
- called. If there were multiple entries or exits this would be an
- additional problem.
-
- GOSUB 100
- ....
- END
-
- 100 ' ----------- subroutine to do stuff...
- { compute stuffs }
- ' are we done?
- IF { final condition achieved } THEN 200
- { compute more stuffs }
- 200 RETURN
-
- Stringing these ideas together we can draw a prototype program:
-
- ' main program
- GOSUB 100 ' init stuff
-
- ' process stuff
- FOR X = 1 to whatever
- GOSUB 200 ' first part of processing
- GOSUB 300
- NEXT
-
- GOSUB 400 ' final stuff
- END
-
- 100 '=============== init
-
- { .... do processing here }
-
- RETURN
-
- 200 '=============== first processing
-
- { .... do processing here }
-
- RETURN
-
- 300 '=============== second processing
-
- { .... do processing here }
-
- RETURN
-
- 400 '=============== final stuff
-
- { .... do processing here }
-
- RETURN
-
- This is a common way to design a relatively straightforward program. It's
- much to be preferred to flowcharts. Here we've outline the structure of
- our entire program without getting bogged down in petty details. Another
- good feature of this design is that we can prototype. We could write the
- initializing section and the final processing section without worrying for
- the moment about the 2 middle sections. We could just put some print
- statements in there to alert us to the fact they need to come later. This
- method, called Top-Down Design starts with the most important elements of
- the program, the overall structure and works in ever more detail. In this
- way, the interactions among the program parts is being tested from the
- start. Try this method with simple programs and you'll find that soon you
- can tackle more complicated projects. If you're interested in more on
- structured programming, look for books by Constantine or Yourdon. Most of
- the ideas in these books can be applied to any language.
-
-
-
- BACK TO OUR SCHEDULED PROGRAMMING....
-
- RENUMBERING (RENUM)
-
- If you're using a compiler, you never need to worry about renumbering. This
- feature alone might be enough to convince you to acquire a compiler.
-
- RENUM is a command in the Basic interpreter that lets you renumber your
- program. It's especially useful since it takes care to maintain any
- references you've set up. All GOTO, RESTORE, IF-THEN and GOSUB relations
- will be the same after the RENUM as they were before. The numbers may be
- different. This is the preferred way of changing numbers. You can do it by
- hand, but if you miss one, it'll be hard to find. The simplest form of
- RENUM is just
-
- RENUM
-
- This renumbers the entire file, using 10 as an increment. If you have a
- large file and want to reorder just part you can use
-
- RENUM [newnum], [oldnum]
-
- This will start at line [oldnum] and change it to [newnum] and continue
- renumbering from there. Try this on a practise file until you feel
- comfortable with the power of this command.
-
- There are 2 philosophies about renumbering. Some people only use the RENUM
- from the start. Here their GOSUB's will often have changed numbers. As
- long as you keep current listings, there's no problem. Others design a
- program with large separations between gosubs. Thus you might define them
- as 1000, 2000, 3000, ..., and interpolate later as necessary. This method
- keeps the subroutines in the same place, but is more tedious to renumber.
- You'd need to issue several commands:
-
- RENUM 1000,1000
- then list to find what used to be 2000
- RENUM 2000,[new 2000 place]
- then list to find what used to be 3000
- RENUM 3000,[new 3000 place], etc
-
- Try this with a file with several GOSUB's. The first method is certainly
- the easiest, and if you're careful to follow the guidelines above your
- program will still be readable.
-
-
- Exercise
- A. Design a program that will take 2 dates, prompting for year, month and
- day. Then calculate the number of days that separate these two dates.
-
- Hint: in our first treatment we ignored leap years. But when you know the
- year, and are looking over several years you don't have that luxury. You'll
- need to include a check for leap year in the new elapsed days gosub.
-
-
- FUNCTIONS
-
- Functions are similar to gosubs in that they provide a means of using the
- same code in multiple ways and places. The difference is that functions are
- defined at the start of a program, and, in interpreted Basic, are limited to
- single lines. The other difference is that there can only be one value
- returned from a function. The format for defining and using a function is:
-
- DEF FNstuff(x) = { whatever the functions going to do }
- ......
- Y = fnstuff(x) + 10
- .....
- if fnstuff(x) then print "text 1" else print "text 2"
-
- Note that a function can be used anywhere a variable might be on the right
- side of an assignment statement or in a conditional. You can't use a
- function on the left hand side though.
-
- Actually we've already used several functions. RND is a function, so is
- CHR$() that we've used to print ascii characters for the boxes. These are
- system functions since they come as part of the language. Other useful
- system functions include STRING$() and SPACE$(). Here's some examples:
-
- 10 for i = 1 to 10
- 20 print SPACE$(i);i
- 30 next
- 40 for i = 0 to 255
- 50 if i<10 and i>12 then print string$((i mod 50) + 1, i)
- 60 next
-
-
- Short exercises
- Why do we use (i mod 50) + 1 instead of just i? why do we need the +1? Why
- don't we print characters 10 to 12? Try taking these out and see why.
-
- Three very useful functions are Left$(), Right$() and Mid$(). These allow
- us to process parts of strings. Try the following:
-
- 10 x$ = "abcdefghijklmnop"
- 20 print left$(x$, 5)
- 30 print right$(x$, 5)
- 40 print mid$(x$, 5,5)
-
- Left$ and right$ give the left- and right-most characters. The length to be
- used is given as the second parameter. Mid$() is more versatile. MID$(x$,
- x, y) returns y characters, starting at the x'th. A final function for now
- is LEN(). This returns the length of a string. Thus if we want to copy all
- but the last 2 characters of a string, we could write
-
- Y$ = left$( x$, len(x$)-2)
-
- We don't even have to know how long x$ is. We should check that x$ is at
- least three long, though.
-
-
- Exercises
- 1. Write gosub that strips blanks from the end of a string x$. print the
- original length and the new length of the string.
-
- 2. Write a gosub that strips multiple blanks from a string, reducing them
- to single blanks. Strip all trailing blanks.
-
-
-
- Writing your own functions is straightforward. All functions must start
- with DEF FN then up to 6 characters to name the function.
-
- DEF FNMAX( a,b) = abs( a >= b ) * a + abs( b > a ) * b
-
- Here's another twist on using conditionals. The phrase abs ( a >= b )
- translates to 0 or 1 depending on the values of a and b. So, if a is larger
- than or equal to b, we'll return
-
- (1 * a) + (0 * b)
-
- Similarly,
-
- DEF FNMIN( a,b) = abs( a <= b ) * a + abs( b < a ) * b
-
- Some guidelines in designing functions: if a variable appears in the
- definition itself, then it can be replaced by whatever is used in calling
- it. Any other variables used in the function take the value of the current
- state of that variable. Thus
-
- x = fnmin( x, 100)
-
- will ensure that x is no bigger than 100. The values X and 100 are sent to
- replace a and b. If we have the function
-
- DEF FNMAX2(a) = abs( a >= b ) * a + abs( b > a ) * b
-
- then we'd call it with
-
- x = fnmax2(x)
-
- and the function would use whatever value b is currently set to. This can
- cause strange happenings in your programs. It's legal, but not recommended
- as good technique.
-
- We can even combine functions in defining a new function:
-
- DEFMINMAX(min, max, x) = fnmin( fnmax( x, min), max)
-
- Does this make sense to you? If not, try to work it through using a call
- like
-
- X = fnminmax(10, 100, X)
-
- This is a useful function that ensures that X is in the range between 10 and
- 100. The function first takes the maximum of X or min, then takes the
- minimum of x or max. Use this in programs like the checkbook program where
- you can define the expected maximum and minimum values.
-
- Function writing is some of the most fun in Basic. You can be quite
- creative. While normally obscure tricks are frowned upon, in functions,
- they're almost okay, as long as you comment them. Once they work, you can
- use them in many different places. Another advantage, is that, with proper
- naming conventions your mainline code will be self-documenting. Which of
- the following is easier to understand?
-
- X = abs( a <= b ) * a + abs( b < a ) * b
-
- or
-
- X = fnmin(a,b)
-
- Do it once. Comment it. Then put it away and call it when needed.
-
-
- Exercise
- Using gosubs or functions, write a routine that accepts a string and returns
- a string with 10 @'s before and behind it (@ is ascii 64). Put a space
- before and after the name. Thus if we send the routine x$ = "Steve" we'll
- get back
-
- "@@@@@@@@@@@@ Steve @@@@@@@@@@@"
-
-
- EXTRA CREDIT
- Here's a project that will also give you an idea of the way spreadsheets
- work:
-
- This project is more difficult than most we've looked at. Even if you
- don't do the actual programming, it would be worthwhile to design a
- prototype on paper so that you understand the methods involved.
-
- Start with the modified totals program from last time which shows 6
- products over 12 months.
-
- 1. Write a 2 functions that take as input the I,J coordinates of the array
- X and return the screen row and column where that element should be
- printed.
- DEF FNROW(i,j) = ???
- DEF FNCOL(i,j) = ???
-
- Now when we want to update element X(I,J) we can write
-
- ROW = FNROW(I,J)
- COL = FNCOL(I,J)
- LOCATE ROW, COL
-
- In fact, we can eliminate the assignments and just use the functions:
-
- LOCATE FNROW(I,J), FNCOL(I,J)
-
- 2. Move the totals calculations to a GOSUB.
-
- 3. Use a gosub to prompt on lines 23 and 24 for the row and col to update.
- Check that the row and columns are valid. Give an error message if
- they're not. Don't allow entry of 0's as we're going to calculate them.
-
-
- 4. Once you have the new row and column, prompt and get a new value, then
- recalculate the totals and use the functions to redisplay only the fields
- that have changed.
-
- One approach would be to get the new row and col I,J then store the old
- value of X()
-
- OLDVAL = X(I,J)
-
- Now recalc the totals:
-
- X(0,J) = X(0,J) - OLDVAL + X(I,J)
- X(I,0) = X(I,0) - OLDVAL + X(I,J)
- X(0,0) = X(0,0) - OLDVAL + X(I,J)
-
- Then redisplay these 3 values plus X(I,J)
-
-
- In outline form:
-
- { get initial values }
- GOSUB 1000 ' calc totals
- { display initial values using functions
- to locate the elements }
-
- WHILE {still asking for more changes}
- GOSUB 2000 ' get new row, col
- GOSUB 1000 ' calc totals
- { use functions to display totals & new value}
- WEND
-
- 1000 '======= calc totals
- RETURN
-
- 2000 '======= get new row & col
- RETURN
-
-
-
- Part 5. FILES
-
- In previous chapters we learned how to structure programs and reuse sections
- of code with GOSUBs and FUNCTIONs. At this point, you've learned enough
- about Basic to write useful programs. However, we still have no way of
- preserving the results of a program. What if we want to keep results from
- one run to another. The solution is the use of files.
-
- We've already used files to save our Basic programs. When you do a normal
- save from within Basic, only the Basic interpreter can use it. If you want
- to see the program in more readable form, you can save it as an ASCII file
- by modifying the save command:
-
- SAVE "progname.bas", A
-
- Try this with one of your programs, using a different name so that you have
- the original and the ascii version. Then exit Basic and use DIR to look at
- the sizes of the programs. Notice that the ascii cersion takes up more
- room. Next use the TYPE command to examine the files:
-
- TYPE "origname.bas"
- TYPE "progname.bas"
-
- If you have an editor or word processor, you'll find that you can modify the
- ascii version but not the original. (If you use an editor, though, you'll
- be responsible for putting in your own line numbers.)
-
- Other common uses of files are to hold data that must remain when the
- computer is turned off, or to handle large quantities of data. Basic
- provides 2 ways to handle files -- sequential and random access.
-
-
- Sequential versus Random
-
- Some of this chapter may seem more techincal than previous installments.
- Don't worry about the details. The important thing is to understand the
- distinctions between the 2 types of files and when each is appropriate.
-
- All files contain records. Records are the repeating elements within a
- file. They in turn are usually broken down into fields. For example, a
- file record for a mailing list program might have fields with the name,
- address and phone number of each person. Sequential and random files handle
- records and fields quite differently. Each has definite advantages and
- drawbacks.
-
- Sequential files read or write from the start of the file and proceed in an
- orderly fashion to the end of the file. Think of them as a cassette tapes.
- In order to find out what's in the tenth record, we need to read the
- preceding 9 records. (We may not do anything with the information we read,
- but we have to read it). Random files give you immediate access to any
- record in the file. A jukebox with its dozens of records is a good example.
- When you request a record, the mechanical arm goes directly to the record
- you requested and plays it. In computer programs, sequential files must
- always be accessed from the beginning, while random files can select any
- record at any time.
-
-
-
- What's in a file?
-
- Consider a file as a group of bytes. Any additional structure is our
- logical description for convenience and understanding. We'll set up a
- simple data record and see how the two types of files deal with it. Our
- data consists of the following fields:
-
- name
- city
- age
- phone
-
- The following program asks for the information to make 3 records, then
- stores them to a file. It then reads that file and displays the results on
- the screen. Ignore the actual commands for the moment, and concentrate on
- what the program is trying to do. First we'll do it sequentially. (see the
- program SEQFILE.BAS)
-
- We read the name, city, age and phone number (lines 30-80), then write them
- to the file (lines 90-120). When we're done, the file physically looks like
- this:
-
- ------------------------------------------------------
- | .name............/.city......./age/.phone.../.name.|
- | ...../.city......./age/.phone.../.name....../.city.|
- | ...../age/.phone... |
- ------------------------------------------------------
-
- However, when we read it back in, it's interpreted this way:
-
- .name............/
- .city......./
- age/
- .phone.../
- .name.. ...../
- .city......./
- age/
- .phone.../
- .name....../
- .city....../
- age/
- .phone...
-
- Note that each record takes up a variable amount of space in the file. Thus
- we have no way of predicting where a particular record begins. If we start
- at the beginning and just read one record, field by field that never
- concerns us. Sequential files are thus most useful when we have either very
- short files, or when we know we'll always want to read the entire file into
- memory. Their main advantage is that they're easy to program and maintain.
-
- We'll look at the organization of a random file, then return to see how to
- program them. The following program performs the same tasks as the first,
- but creates a random access file (See RANDFILE.BAS)
-
- Again, we read the name, city, age and phone number, and write them to the
- file. This time, the file will physically look like this:
-
- ---------------------------------------------------
- | name................city..............agphone...|
- | name................city..............agphone...|
- | name................city..............agphone...|
- ---------------------------------------------------
-
- Even though the file is called random, the data appears ordered! Each
- record consists of 48 bytes or characters. The information is stored
- exactly the same for each record. Thus if we want to find the third record,
- we know that it begins on the 97th byte. There's no need to look at the
- intervening information. We can point directly to the start of the record
- and read it. Random access files are a bit more complex than sequential,
- and take more programming effort to maintain, but they are much more
- flexible. They're best suited to cases where data will be required in no
- particular order.
-
- Random files are also preferred for large files that are frequently updated.
- Consider, if you have 1000 records and change 10 of them, a sequential file
- makes you read all 1000, make the changes, then write all 1000 again.
- That's 2000 disk reads and writes. With a random file, you only need 10
- reads and 10 writes.
-
-
- USING FILES
- Now that we have some idea of why we have two types of files, let's look at
- how to use them:
-
- The programs introduce several new commands and concepts. The first is the
- control of files. The OPEN and CLOSE commands tell DOS which areas of the
- disk to manipulate. You can have several files open at one time. For
- example, you might have a customer file, an invoice file and a pricing file.
- Basic distinguishes among them by assigning a number to each one. The #
- sign in front of the file number is optional, but recommended to distinguish
- it as an identifier rather than a numeric value. OPEN is used by both types
- of files, but has a different syntax for each. CLOSE is used when you're
- finished with a file. When you leave a Basic program, all files will be
- automatically closed, but it's good practise to have your program do it.
-
-
- SEQUENTIAL DETAILS
-
- Sequential files must be opened for either reading or writing. In either
- case, an invisible pointer is maintained that indicates where the next
- record is coming from. The format of the statement is
-
- OPEN filename FOR [ INPUT / OUTPUT / APPEND ] AS handle
-
- where filename is any explicit filename or variable. Usually existing files
- are read first, new information added, then the entire file is written out.
- APPEND is a special form of OUTPUT. Any existing file is kept and new
- information is added to the end of the file. An example might be a file
- that keeps a list of errors encountered during the program. There's no need
- to read in previous errors, but you also don't want to destroy them. So you
- use APPEND to add to the back of the file.
-
- Since the filename can be a variable, you can make your programs more user-
- friendly by showing the user what files are available. For example, if you
- have a series of files that are called MAR.DAT, APR.DAT, etc, you could
- display a list with the FILES command and then prompt for the one the user
- wants. The FILES command puts a directory on the screen. You can use
- wildcards to limit the files displayed:
-
- 10 FILES "*.dat"
- 20 INPUT "Which file to report"; filename$
- 30 OPEN filename$ FOR OUTPUT AS #2
-
- Records are written and recovered with the PRINT #, WRITE #, and INPUT #
- statements. INPUT # reads the requested variables from the given file. You
- have to be careful that the variable types match. Ie, you can't read a
- string into an integer variable. PRINT # has several problems for
- beginners, mostly related to how it formats the fields before writing. The
- Basic manual describes the problems and their solutions if you're in a
- masochistic mood. For our purposes, the WRITE # statement is more useful.
- It encloses each string in quotation marks and separates fields on a line by
- commas. In this example, we've made it even simpler by writing each field
- separately. (WRITE places a linefeed at the end of each operation, so when
- you TYPE TEST.DAT each field will be on a separate line. This uses an extra
- byte per line, but makes data files easier to display and programs easier to
- debug.)
-
-
- RANDOM NOTES
-
- Random files, as illustrated in the second program use the same OPEN
- statement for input and output. After assigning a number, they also require
- a length for each record. The FIELD statement is also required before a
- random file can be used. This command defines the length of each field in a
- record. You should be careful to use different variable names for the field
- elements and for your actual data. There are some places where Basic lets
- you use the same name, but you're taking an unncessary risk of causing bugs.
- Since the field statement defines the length of each field, you have to do a
- little more planning with random files. In the sequential file, we never
- had to consider the length of a name, or whether a phone number had an area
- code attached. Here, we make the conscious decision that names will be only
- 20 characters, and that phone numbers will not have area codes. A special
- case is the storage of numbers. All items in random files are stored in a
- special format. For numbers, this means all integers are stored as 2 bytes.
- Even if the actual number is 4 or 5 digits, it's compressed before storage
- using the functions defined below. (Thus random files are often much
- smaller than sequential files.)
-
- The next difference is seen in the method used to write a record. First all
- fields are determined. Then, each element of the field statement is set up
- using the LSET statement. For strings, this is just an assignment:
-
- LSET field.element = string.variable
-
- For integers, we first need to make the number into a string using the
- special function MKI$()
-
- LSET field.element = MKI$( integer.variable)
-
- (Similar MK functions are available for converting single and double
- precision numbers.)
-
- Then we write this record with the statement
-
- PUT #1, rec.number
-
- Note that we don't even mention the fields. The FIELD statement
- automatically takes current values of n$, c$, a$ and p$ associated with file
- #1 and uses them in GET and PUT statement. Note also, that if, for the next
- record, we just changed the n$, the previous values of the other elements
- would remain.
-
- To read a record, we need only it's position. To read the third
- record:
-
- GET #1, 3
-
- Now the element values are in n$, c$, a$ and p$ so we need to translate them
- to variables that can be used in our program (lines 190-220). The converse
- of MKI$() function is CVI(), or convert integer.
-
-
- That's it! Now we have 2 methods for saving data and several ways to
- manipulate the files.
-
-
- Questions
- *** Add verification to the programs to ensure that the age is within a
- certain range and that all phone numbers are of the form XXX-XXXX.
-
- For the following, write down what you think will happen before trying it
- with the programs.
-
- *** What happens if you enter unanticipated data to each file? Eg,
- what occurs if you enter a 3 or 4 digit age? or a phone number with
- zip code?
-
- *** What happens if the name contains commas or quotation marks?
-
- *** What happens if you open and read a random file in a sequential
- manner or vice versa?
-
-
- PROJECTS
-
- These projects are a little longer than the average, but most of them use
- sections we've done previously. When you finish these you'll have a good
- working understanding of Basic files.
-
- 1. Sequential files
- Use the earlier checkbook programs to create a checkbook file. This
- should use the following records:
-
- check number
- description
- amount
-
- At the start of the program, you'll need to read in the starting balance.
- At the end, you'll have the ending balance. An outline of the program might
- be:
-
- dim check(200), desc$(200), amount(200)
-
- open "checks.dat" for input as #1
- input #1, start.balance
- input #1, n.checks
- for n = 1 to n.checks
- input #1, check(n), desc$(n), amount(n)
- next
- input #1, final.balance
-
- { prompt for transactions, keep a running total }
- close 1
- open "checks.dat" for output as #1
- write #1, start.balance
- write #1, n.checks
- for n = 1 to n.checks
- write #1, check(n), desc$(n), amount(n)
- next
- write #1, final.balance
- close
-
- It would also be nice to have a report program. This would just read the
- file and print a report. You should have the deposits and debits in two
- different columns.
-
- 2. Random files
- Change the earlier name and address example to allow updates and additions.
- In outline form:
-
- open "test2.dat" as #1 len=48
- field 1, .....
-
- prompt for highest record number
- [ normally this would be stored in the file itself,
- but for simplicity, assume that the user must know. ]
-
- prompt "Add or Update or Display?"
-
- If {ADD} then
- {record number?}
- {prompt for information}
- {write record}
-
- If {Update} then
- {record number?}
- {read current information}
- {display current information}
- {information to change?}
- {write record}
-
- If {Display} then
- {record number?}
- {read current information}
- {display current information}
-
- Note that there are several candidates for GOSUBs or FUNCTIONs here. What
- happens if you read a record beyond the end of the file? What if you write
- past the end?
-
- If you don't have the time to do the entire program, implement only one part
- of it. Put in the prompting for the other sections anyway. If those
- sections are selected, then print a short message saying that the selection
- chosen isn't available yet. This is a method that's often used in actual
- software development. You block out the main segments of the program, then
- use stubs to indicate where later functions will be. This way a partial
- program can be tested early rather than trying to debug an entire program at
- once.
-
-
- Part 7: Simple Graphics
-
- In the next few sections, we'll look at the graphics abilities of the IBM
- PC. To fully use these chapters, you'll need access to an IBM with a CGA,
- EGA or VGA board and monitor. These are graphics adapters that allow you to
- go beyond simple text.
-
- WIDTH
-
- So far whenever we've written information to our monitor screen, we haven't
- done anything special. Thus we've accepted Basic default modes of text
- screens with 80 columns. There are several ways we can change these
- defaults. We can set the width of a text screen to be either 40 or 80. In
- 40 column, the letters are larger, so sometimes easier to read. 80 column
- mode is crisper with sharper colors. Both modes have their applications.
- To change from one mode to another, use the WIDTH command
-
- WIDTH screen.width
-
- On a monochrome screen, the width command works, but the size of
- characters doesn't change. All that happens is that display is limited
- to the left hand side of the screen.
-
-
- SCREEN
-
- Let's examine how the IBM screen display is set up. This is specific to the
- IBM PC environment. If a machine fails the compatibility test it's often
- related to how its video display is set up. Any "100% compatible" machine
- must be able to perform all the commands that we'll discuss in these next
- few sections. (You can use the example programs to test their claims when
- shopping!)
-
- The following few paragraphs may tell you more than you want to know about
- the internals of video displays. You can skim them if you wish, then catch
- up with us below (just press F2)
-
- The first thing we'll need to understand is what an attribute is.
- Attributes describe how a character is displayed on the screen. We've
- actually been using attributes without worrying about them up to now. Basic
- lets you do this with the COLOR command. When you enter,
-
- COLOR 15,1
-
- you're telling Basic to set the attribute to 31 which is displayed as
- intense white on a dark blue background. COLOR commands stay in effect
- until the next COLOR command is issued. But why 31? The attribute byte,
- like all bytes can take values from 0 to 255. We can look at it as a series
- of 8 bits, each of which can be 0 or 1. The positions within the byte are
- interpreted as follows:
-
- ┌──┬────────┬──┬────────┐
- │ │ │ │ │
- └──┴──┴──┴──┴──┴──┴──┴──┘
- \ \_____\ \ \______\
- \ \ \ \
- \ \ \ foreground = 000 to 111
- \ \ intensity 0 or 1
- \ background 000 to 111
- blinking 0 or 1
-
- Looks intimidating, but in practise, it's easy to use. It's an extremely
- efficient way to code the foreground color, its intensity, the background
- color, and whether or not the character is blinking, all in one number. The
- foreground color can vary from 0 to 7, in binary terms this means
-
- 000 = 0 black
- 001 = 1 blue
- 010 = 2 green
- 011 = 3 cyan
- 100 = 4 red
- 101 = 5 magenta
- 110 = 6 yellow/brown
- 111 = 7 white
-
- Thus any 3 bits can be used to code for 8 numbers. Next we code the
- intensity by setting the 4'th or 8's bit on if we want intense color,
- leaving it at 0 otherwise. So to code for intense white, we'd use 1111.
- The initial 1 says intense, the next 3 give 7, white's code. Binary 1111 is
- the same as decimal 15, the number we've been using for intense white all
- along. We can also code 8 possible background colors using another 3 bits.
- Since these start in column 5, this is the same as multiplying them be 2 to
- the 4th power = 16. (Column 1 is 2 to the 0th = 1.) That brings our total
- to 7 bits. We use the last, the 128's column bit to show whether or not to
- blink. We've wasted nothing and stored 4 pieces of information in one tiny
- byte. There's no need to remember all the details. To use attributes you
- need only use the following formula:
-
- attribute = 16 * background color + foreground color
- if intense then attribute = attribute + 8
- if blinking then attribute = attribute + 128
-
- We can make this into a function:
-
- DEF FNATT(fgd, bgd, intense, blink) =
- 128 * blink + bgd*16 + intense * 8 + fgd
-
- so if we wanted to calculate the attribute for intense white on blue,
- we'd use:
-
- att = fnatt(7,1,1,0)
-
-
- Okay, the skimmers should have caught up with us by now....
-
- In order to store information on the screen, we need to know what to put
- there, and how to display it. The IBM method stores the information as
- repeating pairs of bytes. Even numbered bytes tell which ascii character,
- odd bytes give their attribute.
-
- If each character needs 2 bytes, then a 25 line screen of width 80 requires
- 4000 bytes. A 40 column screen needs only 2000. However, the IBM video
- display has room to store 16K! Early applications failed to capitalize on
- this extra memory, since the tricks we'll look at now work only with color
- graphics adapters and their successors. It turns out that we can use that
- extra memory to display multiple screens at once. We can think of the IBM
- memory as being a chunk of 16K broken down as follows:
-
- ------------------------------
- | 80 col | 40 col |
- ------------------------------
- |0 |0 |
- | |----- 2K -----|
- | |1 |
- ----- 4K -----|------4K------|
- |1 |2 |
- | |----- 6K -----|
- | |3 |
- ----- 8K -----|----- 8K------|
- |2 |4 |
- | |---- 10K -----|
- | |5 |
- ---- 12K -----|---- 12K------|
- |3 |6 |
- | |---- 14K -----|
- | |7 |
- ---- 16K -----|---- 16K------|
-
- Normally, we'd say that Basic limits us to 64K of code and data in our
- programs. However, the IBM video display can be thought of as an additional
- 16K of memory. We'll look at some ways that extra memory can be used. The
- first and simplest way is just to display information. In the default
- screen of 80 columns and 25 rows, we have 4000 bytes of information.
- Looking at the map, we see that in fact there's room for 4 pages of
- information, numbered 0 to 3. Similarly, the 40 column width gives us 8
- pages, from 0 to 7. The SCREEN command lets us switch among these pages.
-
- SCREEN mode, burst, apage, vpage
-
- For now, the only mode we'll use is 0 which is text mode. The burst is 0
- for RGB screens, 1 for composite. (This is an archaic leftover from early
- video monitors. Set the burst to 1 for any modern system.) The two
- interesting guys are apage and vpage, standing for active page and visual
- page. These can range from 0 to 3 or 7 depending on width. The visual page
- is the screen you're currently showing on your monitor. The active page is
- the page to which your program reads or writes. Normally these are the
- same, but some interesting effects are possible if you vary them. Screens
- are easier to show than explain. (See program SCR.BAS).
-
-
- This program shows how screens work. First we write a line on each of the 4
- screens, switching both active and visual. Then we write to each of the
- screens, while keeping 0 as the active screen. Finally we switch from
- screen to screen without writing anything more, to prove that in fact we
- have done something. Why bother? What happened while the program was
- writing to the other pages? Did you notice a delay? What if you had
- several pages of information, such as instructions that you wanted to store
- for easy reference, and didn't want to rewrite each time? If you stored
- them to an alternate page, you'd only have to write them once, then by just
- shifting screens you could get that information instantly.
-
-
- Exercise
- Using the checkbook balancing program, change the program so that any errors
- are displayed on an alternate page. Write the error first, then shift to
- the page. Keep track of errors and write each one on a new line. (Be
- careful that you don't try to write past line 25!) After each error, shift
- to the page showing accumulated errors, then wait for a keypress before
- coming back to the main program.
-
-
- The commands we'll be learning next are:
-
- CIRCLE
- LINE
- PSET
- PAINT
-
- In addition we'll see new uses for:
-
- COLOR
- SCREEN
-
- We'll look at 2 programs this time that illustrate these commands:
-
- ** PALETTE.BAS shows the combinations of colors we can achieve
-
- ** LINES.BAS shows some of the straighter applications
-
- First we'll look at circles and colors:
-
- 10 'palette.bas
- 15 KEY OFF
- 20 CBK = 1
- 30 PALET = 0
- 40 P = 0
- 50 P2 = 0
- 60 SCREEN 1,P2 : COLOR CBK, PALET : CLS
- 70 GOSUB 250
- 80 X$=INPUT$(1)
- 90 WHILE X$ <> " "
- 100 IF X$ <> "P" AND X$ <> "p" THEN 160
- 110 P = P + 1
- 120 IF P > 2 THEN P = 0
- 130 PALET = P MOD 2
- 140 IF P = 2 THEN P2 = 1 ELSE P2 = 0
- 150 SCREEN 1, P2
- 160 IF X$ <> "B" AND X$ <> "b" THEN 190
- 170 CBK = CBK + 1
- 180 IF CBK > 31 THEN CBK = 0
- 190 COLOR CBK, PALET
- 200 GOSUB 250
- 210 X$ = INPUT$(1)
- 220 WEND
- 230 SCREEN 0,0 : COLOR 15,1
- 240 END
- 250 ' --------- showit
- 260 FOR I = 1 TO 3
- 270 CIRCLE (50 + I* 50, I*40), 30, I
- 280 PAINT (50 + I* 50, I*40), I, I
- 290 NEXT
- 300 LOCATE 21,5: PRINT "screen 1,";P2;
- 310 PRINT " COLOR";CBK;",";PALET
- 320 LOCATE 22,5: PRINT "Use 'B' to change background";
- 330 LOCATE 23,5: PRINT "Use 'P' to cycle palettes";
- 340 LOCATE 24,5: PRINT "Press spacebar when done....";
- 350 RETURN
-
- I use a variation of this routine in several of my games. It gives players
- the ability to configure the colors to their taste (or lack thereof). Let's
- look at how it's done:
-
- First, we'll meet the new players:
-
- SCREEN -- In earlier chapters we used SCREEN 0 to switch among text
- screens. Here, we use SCREEN 1,0 to tell Basic we wish to use graphics.
- This changes the orientation of the screen from 80 by 25 text characters to
- 320 by 200 pixels or dots. This lets us create graphics images, lines and
- patterns.
-
- COLOR -- This command is similar to that used in text mode. However, the
- second argument can only be 0 or 1. The first argument still sets the
- background color.
-
- COLOR 2, 0
-
- This sets the background to green, and uses the 0 palette. CGA Graphics
- mode has two palettes -- 0 uses colors green/red/yellow-brown and 1 uses
- cyan/magenta/white. If you issue a palette change command, the screen stays
- the same and the color switch. An undocumented palette can be achieved by
- issuing the SCREEN 1,1 command. This is illustrated in the program. This
- palette contains cyan/red/white and can make your programs more appealing
- than the universal cyan/magenta of IBM graphics.
-
- CIRCLE -- This command, not surprisingly, draws a circle of given color
- and radius.
-
- CIRCLE (X,Y), radius, color
-
- Here, the cursor will be at X,Y (in pixels), with 0,0 at the upper left
- hand corner. The radius is in pixels, and the color is 0 to 3.
-
-
- PAINT -- fills an area with a color, starting at the indicated point.
- For circles or boxes, the center works well, but it's not required. The
- paint starts at that point and continues in all directions until a line of
- the indicated color is reached. Unfortunately, PAINT has a nasty habit of
- leaking if you try to PAINT an unclosed object, so use it with care.
-
- The next program builds on what we've learned with screen and color and
- adds straight lines and patterns. (See program LINES.BAS, included in the
- shareware package.)
-
- In addition to the commands we met earlier, this program introduces 2
- new ones:
-
- PSET (X,Y), color -- places a dot of color at the indicated pixel. You
- can think of this as anchoring the cursor, also, similar to the LOCATE
- command in text mode.
-
- LINE -- This command has two forms, and several options. It's simplest,
- but longest form is
-
- LINE (x1,y1) - (x2,y2),color
-
- This draws a line from x1,y1 to x2,y2 in this color. From this point, we
- could write
-
- LINE (x2,y2) - (x3,y3),color
-
- to add a new segment starting at x2,y2, or we could just write
-
- LINE - (x3,y3),color
-
- which says to draw the line from the last cursor location to x3,y3. There's
- a simple way of drawing boxes and optionally filling them. Just add the
- commands B or BF to the LINE command:
-
- LINE (x1,y1) - (x2,y2), c, b
- LINE (x1,y1) - (x2,y2), c, bf
-
- Here the two points represent the upper left and lower right corners of a
- box. The 4 lines represented by these corners are drawn automatically.
-
- The exercises this time are a little more involved than previously. This is
- because Basic graphics commands are fun to play with, AND because there are
- several points that can best be made after you've had some time to
- experiment. Even if you don't do any of the exercises, you might find it
- interesting to read through this exercise section.
-
-
- Exercises
-
- A. Create a program that randomly draws boxes and circles on the screen,
- filling them with color. Experiment with different colored borders and
- painting. Watch what happens when borders overlap.
-
- At this point in the series, if you've been doing your homework, you should
- find the previous exercise straightforward. The next project should be more
- challenging!
-
- B. This project is quite similar to the simple game proposed last time. The
- intent this time is slightly different. By using the function keys we can
- create a simple line drawing program.
-
- 1. First draw a large box on the screen. Don't allow the user to cross this
- boundary. (Solution hint: You'll need to calculate the x,y coordinates
- before you draw them & check them against some boundary conditions. For
- example, if you draw the boundary at the outer edge, using
-
- LINE (0,0) - (319,189), 1, B
-
- Then you need to check that any proposed x is between 0 and 319 and any y is
- between 0 and 189.
-
- 2. Use the program outlined last time (for interpreting the arrow keys) to
- create a new program that draws lines. The pseudocode will be:
-
- { draw a point in the middle of the screen}
-
- if {left arrow pressed} then { draw line segment to left}
-
- if { up arrow pressed} then { draw line segment up }
-
- ....
-
- You can make the line segments some constant amount or a random amount.
-
-
- 2. Use F3 & F4 to increase and decrease speed. Remember to check for a
- speed of 0. Don't let the speed go below 0, though.
-
- 3. Use F5 to change the color of the lines being drawn
-
- 4. Use F6 to draw a circle at the current location.
-
- FOR SUPER-EXTRA CREDIT:
- Last time we described a game in which you tried to control a moving cursor
- without hitting characters that were already on the screen. This time we
- didn't include those rules in the game description. Before reading further,
- try to think of reasons why this might not be as simple as it was with
- characters. Can you think of methods by which you could get around these
- exercises? (No need to write the actual programs.)
-
- Ready?
-
- When you draw a line, how can you tell what pixels compose the line between
- the 2 points? One of the strengths of the LINE command is also a drawback.
- We just tell it to draw a line from (x,y) to (x',y'), but we don't have to
- calculate the individual pixels that get drawn. With characters, we know
- exactly which ones to look at. For the line, we'd need to get out our trig
- manuals to calculate exactly which pixels were being covered.
-
- There are algorithms that describe how to tell if 2 lines intersect, or if a
- particular point is on a line, but even using these, we'd be stretching an
- interpreted language to try to do it all in realtime.
-
- Don't feel too bad if you didn't solve this exercise. It's pretty difficult
- to find a solution that's both practical and fast. The point rather is that
- Basic provides an extremely flexible method for investigating such involved
- and difficult exercises. In several of my published games I've wrestled
- with these very problems. My approach is to write short Basic programs
- first, examining the difficulties in detail. When the problem's solved, I
- can then either compile it in PowerBasic or rewrite it in another language.
- So even if my final language is C or Pascal, Basic is often my first choice
- for quick & dirty graphics prototyping.
-
- ============================
-
-
- The End of the Beginning
- This completes the BASIC TRAINING TUTORIAL. We've covered a large number of
- topics, and if you've done some of the exercises, projects and played with
- the example programs, you should have a solid grasp of the elements of Basic
- at this point. What you do next depends on the types of programs you want
- to write. If you're interested in studying more of what Basic can do, and
- what a compiler can add, the Advanced Tutorial will help. When you
- register, you get it for free.
-
- ADVANCED BASIC. Topics include: Animation techniques, Shape Shifting
- techniques, Error Handling, Chaining, Windows, Peek / poke, Bload / bsave,
- Plus, details on differences between interpreters and compilers. And
- complete source for all examples. Advanced Basic is ONLY available to
- registered users.
-
- Registration costs only $20, and you will receive The Advanced Basic
- Tutorial as a bonus, along with more source code for all the examples in
- that tutorial.
-
- In addition, when you register you also get an evaluation copy of the
- LIBERTY Basic compiler for Windows. This program lets you develop Basic
- programs in the Windows environment, without the need for the Windows
- Software Development Kit.
-
- You might also want to order the Games Package option. The Games package
- includes source code for 2 Cascoly programs that you can compile and modify
- for your personal use.
-
- ATC -- An air traffic controller game that's perfect for a few minutes or
- hours of fun. Easy to learn, but difficult to master. The game tracks best
- scores at each of 20 levels of difficulty. Shows how to use real time
- interrupts and error detection.
-
- ECOMASTER -- The CGA version of Cascoly's ecology game. A diverting ecology
- game in which you bid for and trade animals based on their abilities to
- thrive in different environments.
-
- To Register, return to DOS, and enter the command:
- REGISTER
- An order form will be printed for you. Or send $20 + $4 shipping to:
- Cascoly Software
- 4528 36th Ave NE
- Seattle WA 98105
-
-